﻿using System;
using FairyGUI;
using FairyGUI.Utils;
using UnityEngine;

/// <summary>
/// Achieving the effect of turning over books. Use virtual mechanism to support unlimited pages. Support covers.
/// </summary>
public class FairyBook : GComponent
{
	/// <summary>
	/// 
	/// </summary>
	/// <param name="index"></param>
	/// <param name="item"></param>
	public delegate void PageRenderer(int index, GComponent page);

	/// <summary>
	/// 
	/// </summary>
	public PageRenderer pageRenderer;

	/// <summary>
	/// 
	/// </summary>
	public static float EffectDuration = 0.5f;

	/// <summary>
	/// 
	/// </summary>
	public enum Paper
	{
		Soft,
		Hard
	}

	public enum CoverType
	{
		Front,
		Back
	}

	enum CoverStatus
	{
		Hidden,
		ShowingFront,
		ShowingBack
	}

	enum CoverTurningOp
	{
		None,
		ShowFront,
		HideFront,
		ShowBack,
		HideBack
	}

	enum Corner
	{
		INVALID,
		TL,
		BL,
		TR,
		BR
	}

	GComponent _pagesContainer;
	string _pageResource;
	int _pageWidth;
	int _pageHeight;

	int _pageCount;
	int _currentPage;
	Paper _paper;

	int _turningTarget;
	float _turningAmount;
	CoverTurningOp _coverTurningOp;
	GPath _turningPath;

	GComponent[] _objects;
	GGraph _mask1;
	GGraph _mask2;
	GObject _softShadow;
	int[] _objectIndice;
	int[] _objectNewIndice;

	Corner _draggingCorner;
	Vector2 _dragPoint;
	float _touchDownTime;

	GComponent _frontCover;
	GComponent _backCover;
	Vector2 _frontCoverPos;
	Vector2 _backCoverPos;
	CoverStatus _coverStatus;

	EventListener _onTurnComplete;

	public override void ConstructFromXML(XML xml)
	{
		base.ConstructFromXML(xml);

		_pagesContainer = GetChild("pages").asCom;
		if (_pagesContainer == null)
		{
			Debug.LogError("Not a valid book resource");
			return;
		}

		GComponent obj1 = _pagesContainer.GetChild("left").asCom;
		GComponent obj2 = _pagesContainer.GetChild("right").asCom;
		if (obj1 == null || obj2 == null || obj1.resourceURL != obj2.resourceURL
			|| obj1.width != obj2.width || obj2.x != obj1.x + obj1.width)
		{
			Debug.LogError("Not a valid book resource");
			return;
		}

		obj1.displayObject.home = this.displayObject.cachedTransform;
		obj2.displayObject.home = this.displayObject.cachedTransform;
		_pagesContainer.RemoveChild(obj1);
		_pagesContainer.RemoveChild(obj2);

		_frontCover = GetChild("frontCover") as GComponent;
		if (_frontCover != null)
			_frontCoverPos = _frontCover.position;
		_backCover = GetChild("backCover") as GComponent;
		if (_backCover != null)
			_backCoverPos = _backCover.position;

		_objects = new GComponent[4] { obj1, obj2, null, null };
		_objectIndice = new int[4] { -1, -1, -1, -1 };
		_objectNewIndice = new int[4];
		_turningTarget = -1;
		_currentPage = -1;

		_pageWidth = (int)obj1.width;
		_pageHeight = (int)obj1.height;
		_pageResource = obj1.resourceURL;

		_mask1 = new GGraph();
		_mask1.displayObject.home = this.displayObject.cachedTransform;
		_mask1.SetSize(_pageWidth, _pageHeight);

		_mask2 = new GGraph();
		_mask2.displayObject.home = this.displayObject.cachedTransform;
		_mask2.SetSize(_pageWidth, _pageHeight);

		SetupHotspot(GetChild("hotspot_tl"), Corner.TL);
		SetupHotspot(GetChild("hotspot_bl"), Corner.BL);
		SetupHotspot(GetChild("hotspot_tr"), Corner.TR);
		SetupHotspot(GetChild("hotspot_br"), Corner.BR);
	}

	public override void Dispose()
	{
		for (int i = 0; i < 4; i++)
		{
			if (_objects[i] != null)
				_objects[i].Dispose();

		}
		_mask1.Dispose();
		_mask2.Dispose();
		if (_softShadow != null)
			_softShadow.Dispose();

		base.Dispose();
	}

	/// <summary>
	/// 
	/// </summary>
	public EventListener onTurnComplete
	{
		get { return _onTurnComplete ?? (_onTurnComplete = new EventListener(this, "onTurnComplete")); }
	}

	/// <summary>
	/// 
	/// </summary>
	/// <param name="res"></param>
	public void SetSoftShadowResource(string res)
	{
		_softShadow = UIPackage.CreateObjectFromURL(res);
		_softShadow.height = Mathf.Sqrt(Mathf.Pow(_pageWidth, 2) + Mathf.Pow(_pageHeight, 2)) + 60;
		_softShadow.displayObject.home = this.displayObject.cachedTransform;
		_softShadow.sortingOrder = int.MaxValue;
	}

	/// <summary>
	/// 
	/// </summary>
	public Paper pageSoftness
	{
		get { return _paper; }
		set { _paper = value; }
	}

	/// <summary>
	/// 
	/// </summary>
	public int pageCount
	{
		get { return _pageCount; }
		set
		{
			if (_pageCount % 2 != 0)
				throw new System.Exception("Page count must be even!");

			_pageCount = value;
		}
	}

	/// <summary>
	/// 
	/// </summary>
	public int currentPage
	{
		get { return _currentPage; }
		set
		{
			if (value < 0 || value > _pageCount - 1)
				throw new Exception("Page index out of bounds: " + value);

			if (_currentPage != value)
			{
				GTween.Kill(this, true);

				_currentPage = value;
				_coverStatus = CoverStatus.Hidden;

				RenderPages();
			}
		}
	}

	/// <summary>
	/// 
	/// </summary>
	/// <param name="pageIndex"></param>
	public void TurnTo(int pageIndex)
	{
		if (pageIndex < 0 || pageIndex > _pageCount - 1)
			throw new Exception("Page index out of bounds: " + pageIndex);

		GTween.Kill(this, true);

		if (_coverStatus == CoverStatus.ShowingFront)
		{
			_coverTurningOp = CoverTurningOp.HideFront;
			_draggingCorner = Corner.BR;
		}
		else if (_coverStatus == CoverStatus.ShowingBack)
		{
			_coverTurningOp = CoverTurningOp.HideBack;
			_draggingCorner = Corner.BL;
		}

		int tt1 = _currentPage;
		if (_currentPage % 2 == 0)
			tt1--;
		int tt2 = pageIndex;
		if (pageIndex % 2 == 0)
			tt2--;
		if (tt1 == tt2)
		{
			_currentPage = pageIndex;
			_turningTarget = -1;
		}
		else
		{
			_turningTarget = pageIndex;
			if (_turningTarget < _currentPage)
				_draggingCorner = Corner.BL;
			else
				_draggingCorner = Corner.BR;
		}

		if (_draggingCorner == Corner.INVALID)
			return;

		StartTween();
	}

	/// <summary>
	/// 
	/// </summary>
	public void TurnNext()
	{
		GTween.Kill(this, true);

		if (isCoverShowing(CoverType.Front))
			TurnTo(0);
		else if (_currentPage == _pageCount - 1)
			ShowCover(CoverType.Back, true);
		else if (_currentPage % 2 == 0)
			TurnTo(_currentPage + 1);
		else
			TurnTo(_currentPage + 2);
	}

	/// <summary>
	/// 
	/// </summary>
	public void TurnPrevious()
	{
		GTween.Kill(this, true);

		if (isCoverShowing(CoverType.Back))
			TurnTo(_pageCount - 1);
		else if (_currentPage == 0)
			ShowCover(CoverType.Front, true);
		else if (_currentPage % 2 == 0)
			TurnTo(_currentPage - 2);
		else
			TurnTo(_currentPage - 1);
	}

	/// <summary>
	/// 
	/// </summary>
	/// <param name="cover"></param>
	/// <param name="turnEffect"></param>
	public void ShowCover(CoverType cover, bool turnEffect)
	{
		GTween.Kill(this, true);

		if (_frontCover == null)
			return;

		if (turnEffect)
		{
			if (cover == CoverType.Front)
			{
				if (_coverStatus == CoverStatus.ShowingFront)
					return;

				_coverTurningOp = CoverTurningOp.ShowFront;
				_draggingCorner = Corner.BL;
				_currentPage = 0;
			}
			else
			{
				if (_coverStatus == CoverStatus.ShowingBack)
					return;

				_coverTurningOp = CoverTurningOp.ShowBack;
				_draggingCorner = Corner.BR;
				_currentPage = _pageCount - 1;
			}

			StartTween();
		}
		else
		{
			if (cover == CoverType.Front)
			{
				_currentPage = 0;
				_coverStatus = CoverStatus.ShowingFront;
			}
			else
			{
				_currentPage = _pageCount - 1;
				_coverStatus = CoverStatus.ShowingBack;
			}
			RenderPages();
		}
	}

	/// <summary>
	/// 
	/// </summary>
	/// <param name="cover"></param>
	/// <returns></returns>
	public bool isCoverShowing(CoverType cover)
	{
		return cover == CoverType.Front ? (_coverStatus == CoverStatus.ShowingFront) : (_coverStatus == CoverStatus.ShowingBack);
	}

	void StartTween()
	{
		_turningAmount = 0;
		RenderPages();
		Vector2 source = GetCornerPosition(_draggingCorner, _coverTurningOp != CoverTurningOp.None);
		Vector2 target;
		if (_draggingCorner == Corner.TL || _draggingCorner == Corner.BL)
			target = GetCornerPosition(_draggingCorner + 2, _coverTurningOp != CoverTurningOp.None);
		else
			target = GetCornerPosition(_draggingCorner - 2, _coverTurningOp != CoverTurningOp.None);

		if (_turningPath == null)
			_turningPath = new GPath();
		Vector2 mid = new Vector2(source.x + (target.x - source.x) / 2, target.y - 50);
		_turningPath.Create(new GPathPoint(source), new GPathPoint(mid), new GPathPoint(target));
		GTween.To(source, target, EffectDuration).SetUserData(true).SetTarget(this)
			.SetPath(_turningPath)
			.OnUpdate(OnTurnUpdate).OnComplete(OnTurnComplete);
	}

	void OnTurnUpdate(GTweener tweener)
	{
		_dragPoint = tweener.value.vec2;
		_turningAmount = _dragPoint.x / (_coverTurningOp != CoverTurningOp.None ? _frontCover.width * 2 : _pageWidth * 2);
		if (_draggingCorner == Corner.TR || _draggingCorner == Corner.BR)
			_turningAmount = 1 - _turningAmount;
		PlayTurnEffect();
	}

	void OnTurnComplete(GTweener tweener)
	{
		bool suc = (bool)tweener.userData;
		_draggingCorner = Corner.INVALID;
		if (suc && _turningTarget != -1)
			_currentPage = _turningTarget;
		if (suc && _coverTurningOp != CoverTurningOp.None)
		{
			if (_coverTurningOp == CoverTurningOp.ShowFront)
				_coverStatus = CoverStatus.ShowingFront;
			else if (_coverTurningOp == CoverTurningOp.ShowBack)
				_coverStatus = CoverStatus.ShowingBack;
			else
				_coverStatus = CoverStatus.Hidden;
		}
		_coverTurningOp = CoverTurningOp.None;
		_turningTarget = -1;

		RenderPages();

		DispatchEvent("onTurnComplete");
	}

	void PlayTurnEffect()
	{
		if (_coverTurningOp != CoverTurningOp.None)
			PlayCoverEffect();

		if (_turningTarget != -1)
		{
			if (_paper == Paper.Hard)
				PlayHardEffect();
			else
				PlaySoftEffect();
		}
	}

	void PlayCoverEffect()
	{
		float amount = Mathf.Clamp01(_turningAmount);
		float ratio;
		bool isLeft;
		GComponent turningObj = (_coverTurningOp == CoverTurningOp.ShowFront || _coverTurningOp == CoverTurningOp.HideFront) ? _frontCover : _backCover;
		PolygonMesh mesh = GetHardMesh(turningObj);

		if (amount < 0.5f)
		{
			ratio = 1 - amount * 2;
			isLeft = _coverTurningOp == CoverTurningOp.ShowFront || _coverTurningOp == CoverTurningOp.HideBack;
		}
		else
		{
			ratio = (amount - 0.5f) * 2;
			isLeft = _coverTurningOp == CoverTurningOp.HideFront || _coverTurningOp == CoverTurningOp.ShowBack;
		}

		if (turningObj == _frontCover)
			SetCoverStatus(turningObj, CoverType.Front, !isLeft);
		else
			SetCoverStatus(turningObj, CoverType.Back, isLeft);

		mesh.points.Clear();
		mesh.texcoords.Clear();
		if (isLeft)
		{
			float topOffset = 1f / 8 * (1 - ratio);
			float xOffset = 1 - ratio;
			mesh.Add(new Vector2(xOffset, 1 + topOffset));
			mesh.Add(new Vector2(xOffset, -topOffset));
			mesh.Add(new Vector2(1, 0));
			mesh.Add(new Vector2(1, 1));
		}
		else
		{
			float topOffset = 1f / 8 * (1 - ratio);
			mesh.Add(new Vector2(0, 1));
			mesh.Add(new Vector2(0, 0));
			mesh.Add(new Vector2(ratio, -topOffset));
			mesh.Add(new Vector2(ratio, 1 + topOffset));
		}

		mesh.texcoords.AddRange(VertexBuffer.NormalizedUV);
	}

	void PlayHardEffect()
	{
		float amount = Mathf.Clamp01(_turningAmount);
		float ratio;
		bool isLeft;
		GComponent turningObj;
		PolygonMesh mesh;
		if (amount < 0.5f)
		{
			ratio = 1 - amount * 2;
			isLeft = _turningTarget < _currentPage;

			turningObj = _objects[2];
			mesh = GetHardMesh(turningObj);
			GetHardMesh(_objects[3]).points.Clear();
		}
		else
		{
			ratio = (amount - 0.5f) * 2;
			isLeft = _turningTarget > _currentPage;

			turningObj = _objects[3];
			mesh = GetHardMesh(turningObj);
			GetHardMesh(_objects[2]).points.Clear();
		}

		mesh.points.Clear();
		mesh.texcoords.Clear();
		if (isLeft)
		{
			turningObj.x = 0;

			float topOffset = 1f / 8 * (1 - ratio);
			float xOffset = 1 - ratio;
			mesh.Add(new Vector2(xOffset, 1 + topOffset));
			mesh.Add(new Vector2(xOffset, -topOffset));
			mesh.Add(new Vector2(1, 0));
			mesh.Add(new Vector2(1, 1));
		}
		else
		{
			turningObj.x = _pageWidth;

			float topOffset = 1f / 8 * (1 - ratio);
			mesh.Add(new Vector2(0, 1));
			mesh.Add(new Vector2(0, 0));
			mesh.Add(new Vector2(ratio, -topOffset));
			mesh.Add(new Vector2(ratio, 1 + topOffset));
		}

		mesh.texcoords.AddRange(VertexBuffer.NormalizedUV);
	}

	void FlipPoint(ref Vector2 pt, float w, float h)
	{
		switch (_draggingCorner)
		{
			case Corner.TL:
				pt.x = w - pt.x;
				pt.y = h - pt.y;
				break;
			case Corner.BL:
				pt.x = w - pt.x;
				break;
			case Corner.TR:
				pt.y = h - pt.y;
				break;
		}
	}

	void PlaySoftEffect()
	{
		GComponent turningObj1 = _objects[2];
		GComponent turningObj2 = _objects[3];
		PolygonMesh mesh1 = GetSoftMesh(turningObj1);
		PolygonMesh mesh2 = GetSoftMesh(turningObj2);

		/**
		*               a           
		*              /  \         
		* f(0,0)------/    b--g(w,0)
		* |          /     /  |     
		* |         /     /   |     
		* |        c     /    |     
		* |         \   /     |     
		* |          \ /      |     
		* e(0,h)-----d--------h(w,h)
		*/
		Vector2 pa, pb, pc, pd, pe, pf, pg, ph;
		float k, angle;
		bool threePoints = false;

		pc = _dragPoint;
		pe = new Vector2(0, _pageHeight);
		pf = Vector2.zero;
		pg = new Vector2(_pageWidth, 0);
		ph = new Vector2(_pageWidth, _pageHeight);

		FlipPoint(ref pc, _pageWidth * 2, _pageHeight);
		pc.x -= _pageWidth;
		if (pc.x >= _pageWidth)
			return;

		k = (ph.y - pc.y) / (ph.x - pc.x);
		float k2 = 1 + Mathf.Pow(k, 2);
		float min;
		min = ph.x - _pageWidth * 2 / k2;
		if (pc.x < min)
		{
			pc.x = min;
			if (pc.x >= _pageWidth)
				return;
			pc.y = ph.y - k * (ph.x - pc.x);
		}

		min = ph.x - (_pageWidth + _pageHeight * k) * 2 / k2;
		if (pc.x < min)
		{
			pc.x = min;
			if (pc.x >= _pageWidth)
				return;
			pc.y = ph.y - k * (ph.x - pc.x);
		}

		angle = Mathf.Atan(k) * Mathf.Rad2Deg;
		pd = new Vector2(_pageWidth - k2 * (ph.x - pc.x) / 2, _pageHeight);
		pb = new Vector2(pd.x + _pageHeight * k, 0);
		pa = new Vector2();

		if (pb.x > _pageWidth)
		{
			pb.x = _pageWidth;
			pa = new Vector2(_pageWidth, _pageHeight - (_pageWidth - pd.x) / k);
			threePoints = true;
		}

		FlipPoint(ref pa, _pageWidth, _pageHeight);
		FlipPoint(ref pb, _pageWidth, _pageHeight);
		FlipPoint(ref pd, _pageWidth, _pageHeight);
		FlipPoint(ref pc, _pageWidth, _pageHeight);
		if (_draggingCorner == Corner.BL || _draggingCorner == Corner.TL)
			angle = -angle;

		switch (_draggingCorner)
		{
			case Corner.BR:
				{
					turningObj1.SetPivot(0, 0, true);
					turningObj1.position = new Vector2(_pageWidth, 0);

					turningObj2.SetPivot(0, 1, true);
					turningObj2.position = new Vector2(_pageWidth + pc.x, pc.y);
					turningObj2.rotation = 2 * angle;

					if (_softShadow != null)
					{
						_softShadow.SetPivot(1, (_softShadow.height - 30) / _softShadow.height, true);
						_softShadow.position = new Vector2(Vector2.Distance(pc, pd), _pageHeight);
						_softShadow.rotation = -angle;
						if (_softShadow.x > _pageWidth - 20)
							_softShadow.alpha = (_pageWidth - _softShadow.x) / 20;
						else
							_softShadow.alpha = 1;
					}

					mesh1.points.Clear();
					mesh1.Add(pe);
					mesh1.Add(pf);
					mesh1.Add(pb);
					if (threePoints)
						mesh1.Add(pa);
					mesh1.Add(pd);

					mesh2.points.Clear();
					mesh2.Add(new Vector2(Vector2.Distance(pc, pd), _pageHeight));
					mesh2.Add(new Vector2(0, _pageHeight));
					if (threePoints)
						mesh2.Add(new Vector2(0, _pageHeight - Vector2.Distance(pc, pa)));
					else
					{
						mesh2.Add(new Vector2(0, 0));
						mesh2.Add(new Vector2(Vector2.Distance(pg, pb), 0));
					}
					break;
				}
			case Corner.TR:
				{
					turningObj1.SetPivot(0, 0, true);
					turningObj1.position = new Vector2(_pageWidth, 0);

					turningObj2.SetPivot(0, 0, true);
					turningObj2.position = new Vector2(_pageWidth + pc.x, pc.y);
					turningObj2.rotation = -2 * angle;

					if (_softShadow != null)
					{
						_softShadow.SetPivot(1, 30 / _softShadow.height, true);
						_softShadow.position = new Vector2(Vector2.Distance(pc, pd), 0);
						_softShadow.rotation = angle;
						if (_softShadow.x > _pageWidth - 20)
							_softShadow.alpha = (_pageWidth - _softShadow.x) / 20;
						else
							_softShadow.alpha = 1;
					}

					mesh1.points.Clear();
					mesh1.Add(pe);
					mesh1.Add(pf);
					mesh1.Add(pd);
					if (threePoints)
						mesh1.Add(pa);
					mesh1.Add(pb);

					mesh2.points.Clear();
					if (threePoints)
						mesh2.Add(new Vector2(0, Vector2.Distance(pc, pa)));
					else
					{
						mesh2.Add(new Vector2(Vector2.Distance(pb, ph), _pageHeight));
						mesh2.Add(new Vector2(0, _pageHeight));
					}
					mesh2.Add(new Vector2(0, 0));
					mesh2.Add(new Vector2(Vector2.Distance(pc, pd), 0));
					break;
				}
			case Corner.BL:
				{
					turningObj1.SetPivot(0, 0, true);
					turningObj1.position = Vector2.zero;

					turningObj2.SetPivot(1, 1, true);
					turningObj2.position = pc;
					turningObj2.rotation = 2 * angle;

					if (_softShadow != null)
					{
						_softShadow.SetPivot(1, 30 / _softShadow.height, true);
						_softShadow.position = new Vector2(_pageWidth - Vector2.Distance(pc, pd), _pageHeight);
						_softShadow.rotation = 180 - angle;
						if (_softShadow.x < 20)
							_softShadow.alpha = (_softShadow.x - 20) / 20;
						else
							_softShadow.alpha = 1;
					}

					mesh1.points.Clear();
					mesh1.Add(pb);
					mesh1.Add(pg);
					mesh1.Add(ph);
					mesh1.Add(pd);
					if (threePoints)
						mesh1.Add(pa);

					mesh2.points.Clear();
					if (!threePoints)
					{
						mesh2.Add(new Vector2(_pageWidth - Vector2.Distance(pf, pb), 0));
						mesh2.Add(new Vector2(_pageWidth, 0));
					}
					else
						mesh2.Add(new Vector2(_pageWidth, _pageHeight - Vector2.Distance(pc, pa)));
					mesh2.Add(new Vector2(_pageWidth, _pageHeight));
					mesh2.Add(new Vector2(_pageWidth - Vector2.Distance(pc, pd), _pageHeight));
					break;
				}
			case Corner.TL:
				{
					turningObj1.SetPivot(0, 0, true);
					turningObj1.position = Vector2.zero;

					turningObj2.SetPivot(1, 0, true);
					turningObj2.position = pc;
					turningObj2.rotation = -2 * angle;

					if (_softShadow != null)
					{
						_softShadow.SetPivot(1, (_softShadow.height - 30) / _softShadow.height, true);
						_softShadow.position = new Vector2(_pageWidth - Vector2.Distance(pc, pd), 0);
						_softShadow.rotation = 180 + angle;
						if (_softShadow.x < 20)
							_softShadow.alpha = (_softShadow.x - 20) / 20;
						else
							_softShadow.alpha = 1;
					}

					mesh1.points.Clear();
					mesh1.Add(pd);
					mesh1.Add(pg);
					mesh1.Add(ph);
					mesh1.Add(pb);
					if (threePoints)
						mesh1.Add(pa);

					mesh2.points.Clear();
					mesh2.Add(new Vector2(_pageWidth - Vector2.Distance(pc, pd), 0));
					mesh2.Add(new Vector2(_pageWidth, 0));
					if (threePoints)
						mesh2.Add(new Vector2(_pageWidth, Vector2.Distance(pc, pa)));
					else
					{
						mesh2.Add(new Vector2(_pageWidth, _pageHeight));
						mesh2.Add(new Vector2(_pageWidth - Vector2.Distance(pe, pb), _pageHeight));
					}
					break;
				}
		}
	}

	void RenderPages()
	{
		RenderCovers();

		if (_softShadow != null)
			_softShadow.RemoveFromParent();

		int curPage = _currentPage;
		if (curPage % 2 == 0)
			curPage--;

		int leftPage, rightPage, turningPageBack, turningPageFront;
		leftPage = curPage;
		rightPage = leftPage < _pageCount - 1 ? (leftPage + 1) : -1;

		if (_turningTarget != -1)
		{
			int tt = _turningTarget;
			if (tt % 2 == 0)
				tt = tt - 1;

			if (tt == curPage)
			{
				_currentPage = _turningTarget;
				turningPageBack = turningPageFront = -1;
			}
			else if (tt > leftPage)
			{
				turningPageFront = tt;
				turningPageBack = rightPage;
				rightPage = tt < _pageCount - 1 ? (tt + 1) : -1;
			}
			else
			{
				turningPageFront = tt > 0 ? (tt + 1) : 0;
				turningPageBack = leftPage;
				leftPage = tt > 0 ? tt : -1;
			}
		}
		else
		{
			turningPageBack = turningPageFront = -1;
		}

		_objectNewIndice[0] = leftPage;
		_objectNewIndice[1] = rightPage;
		_objectNewIndice[2] = turningPageBack;
		_objectNewIndice[3] = turningPageFront;

		for (int i = 0; i < 4; i++)
		{
			int pageIndex = _objectNewIndice[i];
			if (pageIndex != -1)
			{
				for (int j = 0; j < 4; j++)
				{
					int pageIndex2 = _objectIndice[j];
					if (pageIndex2 == pageIndex)
					{
						if (j != i)
						{
							_objectIndice[j] = _objectIndice[i];
							_objectIndice[i] = pageIndex;

							GComponent tmp = _objects[j];
							_objects[j] = _objects[i];
							_objects[i] = tmp;
						}
						break;
					}
				}
			}
		}

		for (int i = 0; i < 4; i++)
		{
			GComponent obj = _objects[i];
			int oldIndex = _objectIndice[i];
			int index = _objectNewIndice[i];
			_objectIndice[i] = index;
			if (index == -1)
			{
				if (obj != null)
					obj.RemoveFromParent();
			}
			else if (oldIndex != index)
			{
				if (obj == null)
				{
					obj = UIPackage.CreateObjectFromURL(_pageResource).asCom;
					obj.displayObject.home = this.displayObject.cachedTransform;
					_objects[i] = obj;
				}

				_pagesContainer.AddChild(obj);
				pageRenderer(index, obj);
			}
			else
			{
				if (obj.parent == null)
				{
					_pagesContainer.AddChild(obj);
					pageRenderer(index, obj);
				}
				else
					_pagesContainer.AddChild(obj);
			}

			if (obj != null && obj.parent != null)
			{
				Controller c1 = obj.GetController("side");
				if (c1 != null)
				{
					if (index == 0)
						c1.selectedPage = "first";
					else if (index == _pageCount - 1)
						c1.selectedPage = "last";
					else
						c1.selectedPage = (index % 2 == 0) ? "right" : "left";
				}

				if (i == 0 || i == 1)
					SetPageNormal(obj, i == 0);
				else if (_paper == Paper.Soft)
					SetPageSoft(obj, i == 2);
				else
					SetPageHard(obj, i == 2);
			}
		}
	}

	void RenderCovers()
	{
		if (_frontCover != null)
		{
			if (_coverTurningOp == CoverTurningOp.ShowFront || _coverTurningOp == CoverTurningOp.HideFront)
			{
				SetPageHard(_frontCover, true);
				SetCoverStatus(_frontCover, CoverType.Front, _coverTurningOp == CoverTurningOp.HideFront);
			}
			else
			{
				if (_frontCover.displayObject.cacheAsBitmap)
					SetCoverNormal(_frontCover, CoverType.Front);

				SetCoverStatus(_frontCover, CoverType.Front, _coverStatus == CoverStatus.ShowingFront);
			}
		}

		if (_backCover != null)
		{
			if (_coverTurningOp == CoverTurningOp.ShowBack || _coverTurningOp == CoverTurningOp.HideBack)
			{
				SetPageHard(_backCover, true);
				SetCoverStatus(_backCover, CoverType.Back, _coverTurningOp == CoverTurningOp.HideBack);
			}
			else
			{
				if (_backCover.displayObject.cacheAsBitmap)
					SetCoverNormal(_backCover, CoverType.Back);

				SetCoverStatus(_backCover, CoverType.Back, _coverStatus == CoverStatus.ShowingBack);
			}
		}
	}

	void SetupHotspot(GObject obj, Corner corner)
	{
		if (obj == null)
			return;

		obj.data = corner;

		obj.onTouchBegin.Add(__touchBegin);
		obj.onTouchMove.Add(__touchMove);
		obj.onTouchEnd.Add(__touchEnd);
	}

	void SetPageHard(GComponent obj, bool front)
	{
		obj.touchable = false;
		obj.displayObject.cacheAsBitmap = true;
		if (obj.mask != null)
		{
			obj.mask.RemoveFromParent();
			obj.mask = null;
		}

		PolygonMesh mesh = obj.displayObject.paintingGraphics.GetMeshFactory<PolygonMesh>();
		mesh.usePercentPositions = true;
		mesh.points.Clear();
		mesh.texcoords.Clear();
		obj.displayObject.paintingGraphics.SetMeshDirty();

		if (front)
		{
			mesh.points.AddRange(VertexBuffer.NormalizedPosition);
			mesh.texcoords.AddRange(VertexBuffer.NormalizedUV);
		}
	}

	void SetPageSoft(GComponent obj, bool front)
	{
		obj.touchable = false;
		obj.displayObject.cacheAsBitmap = false;
		DisplayObject mask = front ? _mask1.displayObject : _mask2.displayObject;
		obj.mask = mask;

		PolygonMesh mesh = mask.graphics.GetMeshFactory<PolygonMesh>();
		mesh.usePercentPositions = false;
		mesh.points.Clear();
		mesh.texcoords.Clear();
		mask.graphics.SetMeshDirty();

		if (front)
		{
			mesh.Add(new Vector2(0, _pageHeight));
			mesh.Add(Vector2.zero);
			mesh.Add(new Vector2(_pageWidth, 0));
			mesh.Add(new Vector2(_pageWidth, _pageHeight));
		}
		else if (_softShadow != null)
			obj.AddChild(_softShadow);
	}

	void SetPageNormal(GComponent obj, bool left)
	{
		obj.displayObject.cacheAsBitmap = false;
		obj.touchable = true;
		obj.SetPivot(0, 0, true);
		if (left)
			obj.SetXY(0, 0);
		else
			obj.SetXY(_pageWidth, 0);
		obj.rotation = 0;
		if (obj.mask != null)
		{
			obj.mask.RemoveFromParent();
			obj.mask = null;
		}
	}

	void SetCoverStatus(GComponent obj, CoverType coverType, bool show)
	{
		Controller c = obj.GetController("side");
		if (show)
		{
			if (c.selectedIndex != 0)
			{
				obj.position = coverType == CoverType.Front ? _backCoverPos : _frontCoverPos;
				obj.parent.SetChildIndexBefore(obj, obj.parent.GetChildIndex(_pagesContainer) + 1);
				c.selectedIndex = 0; //front

				if (obj.displayObject.cacheAsBitmap)
					obj.displayObject.cacheAsBitmap = true; //refresh
			}
		}
		else
		{
			if (c.selectedIndex != 1)
			{
				obj.position = coverType == CoverType.Front ? _frontCoverPos : _backCoverPos;
				obj.parent.SetChildIndexBefore(obj, obj.parent.GetChildIndex(_pagesContainer));
				c.selectedIndex = 1; //back

				if (obj.displayObject.cacheAsBitmap)
					obj.displayObject.cacheAsBitmap = true; //refresh
			}
		}
	}

	void SetCoverNormal(GComponent obj, CoverType coverType)
	{
		obj.position = coverType == CoverType.Front ? _frontCoverPos : _backCoverPos;
		obj.displayObject.cacheAsBitmap = false;
		obj.touchable = true;
		obj.parent.SetChildIndexBefore(obj, obj.parent.GetChildIndex(_pagesContainer));
		obj.GetController("side").selectedIndex = 1; //back
	}

	PolygonMesh GetHardMesh(GComponent obj)
	{
		obj.displayObject.paintingGraphics.SetMeshDirty();
		return obj.displayObject.paintingGraphics.GetMeshFactory<PolygonMesh>();
	}

	PolygonMesh GetSoftMesh(GComponent obj)
	{
		obj.mask.graphics.SetMeshDirty();
		return obj.mask.graphics.GetMeshFactory<PolygonMesh>();
	}

	void UpdateDragPosition(Vector2 pos)
	{
		if (_coverTurningOp != CoverTurningOp.None)
		{
			_dragPoint = GlobalToLocal(pos) - _frontCoverPos;
			_turningAmount = _dragPoint.x / (2 * _frontCover.width);
		}
		else
		{
			_dragPoint = _pagesContainer.GlobalToLocal(pos);
			_turningAmount = _dragPoint.x / (2 * _pageWidth);
		}

		if (_draggingCorner == Corner.TR || _draggingCorner == Corner.BR)
			_turningAmount = 1 - _turningAmount;
	}

	Vector2 GetCornerPosition(Corner corner, bool isCover)
	{
		float w = isCover ? _frontCover.width : _pageWidth;
		float h = isCover ? _frontCover.height : _pageHeight;
		Vector2 pt;
		switch (corner)
		{
			case Corner.BL:
				pt = new Vector2(0, h);
				break;

			case Corner.TR:
				pt = new Vector2(w * 2, 0);
				break;

			case Corner.BR:
				pt = new Vector2(w * 2, h);
				break;

			default:
				pt = Vector2.zero;
				break;
		}

		return pt;
	}

	void __touchBegin(EventContext context)
	{
		GTween.Kill(this, true);

		_draggingCorner = (Corner)((GObject)context.sender).data;
		if (_draggingCorner == Corner.TL || _draggingCorner == Corner.BL)
		{
			if (_coverStatus == CoverStatus.ShowingBack)
			{
				_coverTurningOp = CoverTurningOp.HideBack;
			}
			else if (_objectNewIndice[0] == -1)
			{
				if (_frontCover != null && _coverStatus != CoverStatus.ShowingFront)
					_coverTurningOp = CoverTurningOp.ShowFront;
				else
					_draggingCorner = Corner.INVALID;
			}
			else
			{
				_turningTarget = _objectNewIndice[0] - 2;
				if (_turningTarget < 0)
					_turningTarget = 0;
			}
		}
		else
		{
			if (_coverStatus == CoverStatus.ShowingFront)
			{
				_coverTurningOp = CoverTurningOp.HideFront;
			}
			else if (_objectNewIndice[1] == -1)
			{
				if (_backCover != null && _coverStatus != CoverStatus.ShowingBack)
					_coverTurningOp = CoverTurningOp.ShowBack;
				else
					_draggingCorner = Corner.INVALID;
			}
			else
			{
				_turningTarget = _objectNewIndice[1] + 1;
			}
		}

		if (_draggingCorner != Corner.INVALID)
		{
			_touchDownTime = Time.unscaledTime;
			UpdateDragPosition(context.inputEvent.position);
			RenderPages();
			PlayTurnEffect();

			context.CaptureTouch();
		}
	}

	void __touchMove(EventContext context)
	{
		if (_draggingCorner != Corner.INVALID)
		{
			UpdateDragPosition(context.inputEvent.position);
			PlayTurnEffect();
		}
	}

	void __touchEnd(EventContext context)
	{
		if (_draggingCorner != Corner.INVALID)
		{
			bool suc = _turningAmount > 0.4f || (Time.unscaledTime - _touchDownTime < 0.35f);
			Vector2 target;
			if (suc)
			{
				if (_draggingCorner == Corner.TL || _draggingCorner == Corner.BL)
					target = GetCornerPosition(_draggingCorner + 2, _coverTurningOp != CoverTurningOp.None);
				else
					target = GetCornerPosition(_draggingCorner - 2, _coverTurningOp != CoverTurningOp.None);
			}
			else
				target = GetCornerPosition(_draggingCorner, _coverTurningOp != CoverTurningOp.None);

			float duration = Mathf.Max(EffectDuration * 0.5f, Mathf.Abs(target.x - _dragPoint.x) / (_pageWidth * 2) * EffectDuration);
			GTween.To(_dragPoint, target, duration).SetTarget(this).SetUserData(suc)
				.OnUpdate(OnTurnUpdate).OnComplete(OnTurnComplete);
		}
	}
}